package com.mobimap.nhom9.gui;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.GestureDetector.OnGestureListener;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.widget.Toast;

import com.mobimap.nhom9.dto.LocationDTO;
import com.mobimap.nhom9.dto.PlaceExDTO;
import com.mobimap.nhom9.gui.utls.LocateUtils;

public class MapView extends SurfaceView implements OnGestureListener,
		SurfaceHolder.Callback {

	private SurfaceHolder holder;
	private DrawSurfaceViewThread mySurfaceViewThread;
	private boolean hasSurface;
	private GestureDetector gestureDetector;
	private float x;
	private float y;
	// Demo directions
	private LocationDTO firstLoc;
	private LocationDTO endLoc;

	public MapView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		init();
	}

	public MapView(Context context, AttributeSet attrs) {
		super(context, attrs);
		init();
	}

	public MapView(Context context) {
		super(context);
		init();
	}

	private void init() {
		holder = getHolder();
		holder.addCallback(this);
		hasSurface = false;
		gestureDetector = new GestureDetector(this);

		x = 26099 * 256;
		y = 15397 * 256;
	}

	public void resume() {
		if (mySurfaceViewThread == null) {
			mySurfaceViewThread = new DrawSurfaceViewThread(new Handler(),
					holder, (int) x, (int) y);
			if (hasSurface == true) {
				mySurfaceViewThread.start();
				if (this.firstLoc != null && this.firstLoc != null) {
					mySurfaceViewThread
							.setDirection(this.firstLoc, this.endLoc);
				}

				if (this.place != null) {
					mySurfaceViewThread.setMaker(this.place);
				}

			}
		}
	}

	public void pause() {
		if (mySurfaceViewThread != null) {
			mySurfaceViewThread.requestExitAndWait();
			mySurfaceViewThread = null;
		}
	}

	public void surfaceCreated(SurfaceHolder holder) {
		hasSurface = true;
		resume();
	}

	public void surfaceDestroyed(SurfaceHolder holder) {
		hasSurface = false;
		pause();
	}

	public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
		if (mySurfaceViewThread != null)
			mySurfaceViewThread.onWindowResize(w, h);
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		return gestureDetector.onTouchEvent(event);
	}

	public boolean onDown(MotionEvent e) {

		return true;
	}

	public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
			float velocityY) {

		return false;
	}

	public void onLongPress(MotionEvent e) {
		if (mySurfaceViewThread != null)
			mySurfaceViewThread.setClickPoint(new Point((int) e.getX(), (int) e
					.getY()));
	}

	public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
			float distanceY) {

		mySurfaceViewThread.onScroll(e1, e2, (int) distanceX, (int) distanceY);
		return true;
	}

	public void onShowPress(MotionEvent e) {
	}

	public boolean onSingleTapUp(MotionEvent e) {
		return false;
	}

	public Point getPosition() {

		Point p = new Point((int) x, (int) y);
		if (this.mySurfaceViewThread != null)
			p = this.mySurfaceViewThread.getPosition();
		return p;
	}

	public LocationDTO getCenterLocation() {
		Point p = getCenterPosition();
		LocationDTO l = LocateUtils.ToGeographicCoordinate(p, Container
				.getInstance().getZoom());
		return l;
	}

	public Point getCenterPosition() {
		Point p = getPosition();
		p.x += (getWidth() / 2);
		p.y += (getHeight() / 2);
		return p;
	}

	public void setCenterLocation(LocationDTO location) {
		Point p = LocateUtils.ToPoint(location, Container.getInstance()
				.getZoom());
		setCenterPosition(p);
	}

	public void setCenterPosition(Point p) {
		setCenterPosition(p.x, p.y);
	}

	public void setCenterPosition(int x, int y) {
		setPosition(x - getWidth() / 2, y - getHeight() / 2);
	}

	public void setPosition(Point p) {
		setPosition(p.x, p.y);
	}

	public void setPosition(int x, int y) {
		if (mySurfaceViewThread != null)
			this.mySurfaceViewThread.setPosition(x, y);
		this.x = x;
		this.y = y;
	}

	/**
	 * set Directions of two node
	 * 
	 * @param firstNode
	 * @param endNode
	 */
	public void setDirections(LocationDTO firstNode, LocationDTO endNode) {
		this.firstLoc = firstNode;
		this.endLoc = endNode;
	}

	private PlaceExDTO place;

	public void setMarker(PlaceExDTO place) {
		this.place = place;
	}

	class DrawSurfaceViewThread extends Thread {
		private boolean notCompleted;
		private int currentX_this;
		private int currentY_this;

		private int xTileCount_this = 1;
		private int yTileCount_this = 1;
		private SurfaceHolder surfaceHolder;
		private Paint emptyArea;

		private int distanceX_this;
		private int distanceY_this;
		private boolean requireRedraw;
		private Rect preDrawRectangle;
		private Bitmap buffer1_this;
		private Canvas canvasBuffer1_this;
		private Canvas canvasBuffer2_this;
		private Bitmap buffer2_this;

		private List<Sprite> sprites;
		private Point clickPoint;
		private Handler handler;

		DrawSurfaceViewThread(Handler handler, SurfaceHolder surfaceholder,
				int x, int y) {
			super();
			this.notCompleted = true;
			this.surfaceHolder = surfaceholder;
			this.emptyArea = new Paint();
			this.emptyArea.setColor(Color.WHITE);

			this.currentX_this = x;
			this.currentY_this = y;

			this.setSize(MapView.this.getWidth(), MapView.this.getHeight());
			this.requireRedraw = true;
			this.preDrawRectangle = new Rect(-1, -1, -1, -1);

			sprites = new ArrayList<Sprite>();
			this.handler = handler;

		}

		public void onScroll(MotionEvent e1, MotionEvent e2, int distanceX,
				int distanceY) {
			synchronized (this) {
				this.distanceX_this += distanceX;
				this.distanceY_this += distanceY;
			}
		}

		public Point getPosition() {
			int x = 0;
			int y = 0;
			synchronized (this) {
				x = this.currentX_this + this.distanceX_this;
				y = this.currentY_this + this.distanceY_this;
			}
			Point p = new Point(x, y);
			return p;
		}

		public void setPosition(int x, int y) {
			synchronized (this) {
				this.distanceX_this = x - currentX_this;
				this.distanceY_this = y - currentY_this;
			}
		}

		private void setSize(int w, int h) {
			synchronized (this) {
				this.xTileCount_this = Math.max(w, Container.TILE_WIDTH)
						/ Container.TILE_WIDTH + 2;
				this.yTileCount_this = Math.max(h, Container.TILE_HEIGHT)
						/ Container.TILE_HEIGHT + 2;
				cleanResourceBuffer();

				this.buffer1_this = Bitmap.createBitmap(w, h, Config.RGB_565);
				this.canvasBuffer1_this = new Canvas(buffer1_this);
				this.canvasBuffer1_this.drawColor(Color.WHITE);

				this.buffer2_this = Bitmap.createBitmap(w, h, Config.RGB_565);
				this.canvasBuffer2_this = new Canvas(buffer2_this);
				this.canvasBuffer2_this.drawColor(Color.WHITE);
			}
		}

		@Override
		public void run() {
			while (notCompleted) {
				int x = 0;
				int y = 0;
				int dx = 0;
				int dy = 0;
				int xTileCount = 0;
				int yTileCount = 0;
				int xStartTile = 0;
				int yStartTile = 0;
				Canvas canvasBuffer1 = null;
				Canvas canvasBuffer2 = null;
				Bitmap buffer1 = null;
				Bitmap buffer2 = null;
				boolean isRedraw = false;
				int zoom = 0;
				synchronized (this) {
					zoom = Container.getInstance().getZoom();
					buffer1 = buffer1_this;
					canvasBuffer1 = canvasBuffer1_this;

					buffer2 = buffer2_this;
					canvasBuffer2 = canvasBuffer2_this;

					xTileCount = xTileCount_this;
					yTileCount = yTileCount_this;

					dx = distanceX_this;
					dy = distanceY_this;

					distanceX_this -= dx;
					distanceY_this -= dy;

					currentX_this += dx;
					currentY_this += dy;

					xStartTile = currentX_this / Container.TILE_WIDTH;
					yStartTile = currentY_this / Container.TILE_HEIGHT;

					x = currentX_this;
					y = currentY_this;

					isRedraw = requireRedraw;
					requireRedraw = false;
				}

				if (!isRedraw && dx == 0 && dy == 0) {
					Point p = null;
					synchronized (this) {
						if (this.clickPoint != null)
							p = clickPoint;
						clickPoint = null;
					}
					if (p != null) {
						synchronized (sprites) {
							Rect rect = new Rect();

							Bitmap mark = Container.getInstance()
									.getMapProvider().getMarker();
							int w = mark.getWidth();
							int h = mark.getHeight();
							for (Iterator<Sprite> iterator = sprites.iterator(); iterator
									.hasNext();) {

								final Sprite s = (Sprite) iterator.next();
								if (s instanceof Marker) {
									Point sp = LocateUtils.ToPoint(
											s.getLocation(), zoom);
									rect.left = sp.x - x;
									rect.right = rect.left + w;
									rect.top = sp.y - y;
									rect.bottom = rect.top + h;
									if (rect.contains(p.x, p.y)) {
										final LocationDTO l = new LocationDTO(s
												.getLocation().getLatitude(), s
												.getLocation().getLongitude());
										handler.post(new Runnable() {

											public void run() {
												Toast.makeText(getContext(),
														l.toString(),
														Toast.LENGTH_LONG)
														.show();
											}
										});
									}
									break;
								}
							}
						}
					}
					try {
						System.gc();
						Thread.sleep(100);
					} catch (InterruptedException e) {
					}
					continue;
				}
				drawSurfaceBitmap(buffer1, -dx, -dy);
				canvasBuffer2.drawColor(Color.WHITE);
				canvasBuffer2.drawBitmap(buffer1, -dx, -dy, null);
				canvasBuffer1.drawBitmap(buffer2, 0, 0, null);
				synchronized (this) {
					if (distanceX_this != 0 || distanceY_this != 0)
						continue;
				}
				int xStartPaint = xStartTile * Container.TILE_WIDTH - x;
				int yStartPaint = yStartTile * Container.TILE_HEIGHT - y;
				for (int i = 0; i < xTileCount; i++) {

					synchronized (this) {
						if (distanceX_this != 0 || distanceY_this != 0)
							break;
					}
					int xTile = xStartTile + i;

					int left = xStartPaint + Container.TILE_WIDTH * i;
					for (int j = 0; j < yTileCount; j++) {
						synchronized (this) {
							if (distanceX_this != 0 || distanceY_this != 0) {
								i = xTileCount;
								break;
							}
						}
						int yTile = yStartTile + j;

						int top = yStartPaint + Container.TILE_HEIGHT * j;
						Bitmap bitmap = Container.getInstance()
								.getMapProvider()
								.getTileBitmap(zoom, xTile, yTile);
						Rect dirty = new Rect(left, top, left
								+ Container.TILE_WIDTH, top
								+ Container.TILE_HEIGHT);
						if (bitmap != null) {
							canvasBuffer1.drawBitmap(bitmap, left, top, null);
							drawSurfaceBitmap(bitmap, dirty, left, top);
							bitmap.recycle();
						} else {
							// drawSurfaceRect(dirty);
							// canvasBuffer1.drawRect(dirty, emptyArea);
						}
					}
					synchronized (sprites) {
						int len = sprites.size();
						for (int k = 0; k < len; k++) {
							Sprite s = sprites.get(k);
							synchronized (this) {
								if (distanceX_this != 0 || distanceY_this != 0) {
									break;
								}
							}
							Canvas canvas = surfaceHolder.lockCanvas();
							try {

								Bitmap s_bitmap = s.getBitmap();
								Point sp = locationToPointView(s.getLocation());
								canvas.drawBitmap(s_bitmap, sp.x - x, sp.y - y,null);
								
							} finally {
								surfaceHolder.unlockCanvasAndPost(canvas);
							}
						}
					}
				}

				preDrawRectangle.set(xStartTile, yStartTile, xStartTile
						+ xTileCount - 1, yStartTile + yTileCount - 1);
			}
			cleanResourceBuffer();
		}

		public void addSprite(Sprite s) {
			synchronized (sprites) {
				sprites.add(s);
			}

		}

		public void addAllSprite(Collection<? extends Sprite> ss) {
			synchronized (sprites) {
				sprites.addAll(ss);
			}
		}

		public void removeSprite(Sprite s) {
			synchronized (sprites) {
				sprites.remove(s);
			}
		}

		public void clearSprites() {
			synchronized (sprites) {
				sprites.clear();
			}
		}

		private void cleanResourceBuffer() {
			synchronized (this) {
				if (buffer1_this != null) {
					buffer1_this.recycle();
				}
				if (buffer2_this != null) {
					buffer2_this.recycle();
				}
				buffer1_this = null;
				buffer2_this = null;
			}
		}

		private void drawSurfaceBitmap(Bitmap bitmap, int top, int left) {
			Canvas canvas = surfaceHolder.lockCanvas();
			try {
				canvas.drawColor(Color.WHITE);
				canvas.drawBitmap(bitmap, top, left, null);
			} finally {
				surfaceHolder.unlockCanvasAndPost(canvas);
			}
		}

		private void drawSurfaceBitmap(Bitmap bitmap, Rect dirty, int dx, int dy) {
			Canvas canvas = surfaceHolder.lockCanvas(dirty);
			try {
				canvas.drawBitmap(bitmap, dx, dy, null);
			} finally {
				surfaceHolder.unlockCanvasAndPost(canvas);
			}
		}

		public void requestExitAndWait() {

			notCompleted = false;
			boolean retry = true;
			while (retry) {
				try {
					join();
					retry = false;
				} catch (InterruptedException e) {
				}
			}
		}

		public void onWindowResize(int w, int h) {
			setSize(w, h);
		}

		public void setDirection(LocationDTO first, LocationDTO end) {
			// Marker mark = new Marker(first);
			// this.addSprite(mark);
		}

		public void setMaker(PlaceExDTO markLoc) {
			Marker mark = new Marker(markLoc.getLocation());
			this.addSprite(mark);
		}

		private Point locationToPointView(LocationDTO dto) {
			return LocateUtils.ToPoint(dto, Container.getInstance().getZoom());
		}

		public void setClickPoint(Point clickPoint) {
			synchronized (this) {
				this.clickPoint = clickPoint;
			}
		}
	}

}
